DemoMaking for beginnerz!

Part II

Poisoned Brain

Intro ...

Ok, Dudes! It's time to start our 2nd "lecture"!

In this article we'll cover basic aspects of a computer graphics. The very beginning. So called basis. As you'll soon understand, principles I'm gonna to explain, could be used in any context from DirectDraw to VESA under DOS (or even in Linux)! But for easier understanding of this, I'll start from MODE 13h! After learning of basic principles we'll try to create a simple demo with effect of star sky.

Setting of a video mode!

The first thing we need to do, if we wanna to draw something on screen, it's to set graphic mode, which we'll use. We will use mode 13h. As far as I know, this mode is supported by all available videocards from the last six years. So, we are gonna to set the video mode! Let's try this:

   union REGS regs; 
   regs.x.ax = 0x13; 
   int86( 0x10, ®s, ®s ); 

What are we seeing here? Soo.... Let's look! On ASM this looks like:

mov ax,13h 
int 10h 

What does this do? It does the following! We load register AX with the value of chosen video mode, and then call 10th interrupt! And that's all! In the result we get MODE 13h (320x200x8bit)! Returning in text (3th) mode is done likewise, we only need to replace 13h by 03h. :-)

Video memory!

In 13h mode video memory always begins from address A000h. This means that it begins from this address ON ALL PC and we could draw pixels without worrying where it is. As soon as we use DJGPP in protected mode, we need to add __djgpp_conventional_base to A000h and we will get right pointer onto beginning of a video memory!

Now we could treat video memory as a big array of numbers. The first pixel is stored with offset 0 and remaining part of the first line goes to RESX-1. Next line begins on RESX and goes to RESX*2-1 etc where RESX is horizontal resolution. Using this information we could easily draw point on screen:

   void PutPixel( int x, int y, unsigned char c ) 
   { 
       if ((x<0) || (x>=RESX) || (y<0) || (y>=RESY)) return; 
       video_buffer[y*RESX+x] = c; 
   } 

But! We need to set video_buffer as array of bytes (for eight bit mode). If you don't know how to do this, don't loose yourself! Continue reading and everything will come to its place!

Setting up the Pallette! Wow!

In 8-bit mode each pixel could contain one from 2^8=256 colors. Every such color is an index in array with 256 elements, called palette, and every element has its own RGB values.

In 13h mode color components are limited by 6-bit range [0.63]. Not hard to calculate that you could choose 256 colors from 262144 possible! RGB parameters are simply loaded into videocard through port 3C9h. How could this be done?

void SetColour( unsigned char i, unsigned char r, unsigned char g, unsigned char b ) 
{ 
     outp( 0x3c8, i );                           // Enter number of color 
     outp( 0x3c9, r );                           // red component
     outp( 0x3c9, g );                           // green component 
     outp( 0x3c9, b );                           // blue component 
}; 

Now you could set all 256 colors at once, by calling this procedure for each of them!

In ASM this looks like:

mov dx,03c8h 
mov ah, [i] 
out dx,ah 
inc dx ; or mov dx,03c9h 
mov ah, [r] 
out dx,ah 
mov ah, [g] 
out dx,ah 
mov ah, [b] 
out dx,ah 

Double Buffering.

Screen is updated with the content of video memory each so-called "refresh". The frequency of such updates depends on 'refresh rate" of your video card and monitor (usually it's 85 times per second [85Hz]). If to draw information directly into video memory, you could run against situation when screen will be updated when you will draw on it, you'll get flickering. How to avoid this? The answer is easy - you need to create temporary buffer and draw stuff there and after you've finished you should copy it in video memory. Possibility of running against the refresh is quite small, but it is. This sometimes happens and screen is updated with parts of new and old image.

An easy solution of this problem is procedure "Wait_Retrace" (in this tutorial it is combined with Update, therefore it is called Update):

void Update() 
{ 
   while (inportb(0x3da) & 0x08);    // we wait for retrace 
   while (!(inportb(0x3da) & 0x08)); // second part of Wait_Retrace 
   memcpy( video_buffer, page_draw, 64000 ); // and Update (copy information from video 
};                                           // buffer into videomemory

This procedure waits for the end of "Vertical screen retrace" and then copies information from buffer to videomemory. Certainly, it should be considered that such methods slow down your demo very much and that in new versions THIS ISN'T A PROBLEM ANYMORE. " "APIs like DirectDraw and VBE 3.0 are capable of doing this for you!!!".

Star Field Effect!!!!

Here's it! First demo! Ones who waited - have tarried and those who didn't wait have felt surd kick caused by oblique object in the area of nape! (this is lyrical digression)

The basic idea is that we imagine every star as a pixel on screen. Each star with coordinates (x,y) should be into the limits of the screen. As soon as our stars will move, we will need to check their coordinates. If star flows out from screen then we generate a new one by means of random numbers (in the limits of screen, of course!)

Let's dip inside! Here's it:

// Include required header files: 
#include <iostream.h> 
#include <stdlib.h>    // for rand() function
#include <conio.h>     // for kbhit() 
#include "vga.h" 

// Maximal number of stars
#define MAXSTARS 256 

// Create XStars structure for each star
struct XStar 
{ 
       float x, y;             // star position
       unsigned char plane;    // plane number (there are 3 of them
			       // (back, middle, front)
}; 

// Pointer to array
XStar *stars; 

// All graphics procedure in one line :-): 
VGA *vga; 

// main module: 
int main() 
{ 
// Allocate memory for all stars:
    stars = new XStar[MAXSTARS]; 
// Generate random stars
    for (int i=0; i<MAXSTARS; i++) 
    { 
        stars[i].x = rand() % 320; 
        stars[i].y = rand() % 200; 
        stars[i].plane = rand() % 3;     // Set plane [and color]
    } 
// Set 13h mode [320x200x8bit] 
    vga = new VGA; 
// Set 4 colors, from black to whie
    vga->SetColour( 0,  0,  0,  0 );  // black 
    vga->SetColour( 1, 24, 24, 24 );  // dark grey
    vga->SetColour( 2, 48, 48, 48 );  // light grey
    vga->SetColour( 3, 63, 63, 63 );  // white
// run main cycle:
    while (!kbhit()) 
    { 
    // Clear temporary buffer (look Double Buffering)
       vga->Clear(); 
    // Refresh all stars
       for (int i=0; i<MAXSTARS; i++) 
       { 
        // Move star with number [i] to the right, velocity depends on plane number
           stars[i].x += (1+(float)stars[i].plane)*0.15; 
        // Check if the current star has flown from the limits of screen
           if (stars[i].x>320) 
           { 
           // If yes then move it to the left
              stars[i].x = 0; 
           // and change y coordinate by random numbers
              stars[i].y = rand() % 200; 
           } 
        // Draw star with color which depends on plane number
           vga->PutPixel( (int)stars[i].x, (int)stars[i].y, 1+stars[i].plane ); 
       } 
    // Copy information from buffer to screen
       vga->Update(); 
    } 
// Return to text mode
    delete (vga); 
// Clear memory
    delete [] (stars); 
// That's all!!!!! 
    return 0; 
} 

As you have probably noticed, in the beginning of the program was the line '#include "vga.h"'. But your compiler of course can't find this vga.h!!! What a shit? Right! Don't hurry to compile! There are also two files - vga.cpp and vga.h Here they are:

-=[vga.cpp]=- 
  

#include <dpmi.h> 
#include <dos.h> 
#include <malloc.h> 
#include <string.h> 
#include <sys/nearptr.h> 
#include "vga.h" 
  

/* 
 * Set 320x200x8 mode and prepare Double Buffering 
 */ 
VGA::VGA() 
{ 

// set 0x13 mode

   union REGS regs; 
   regs.x.ax = 0x13; 
   int86( 0x10, ®s, ®s ); 

// set Double Buffering 

   page_draw = new (unsigned char)[64000]; 

// Clear buffer 

   memset( page_draw, 0, 64000 ); 

// Enable access to memory we use
// !!!FOR DJGPP ONLY!!! 

   __djgpp_nearptr_enable(); 

// Find pointer to video memory
// !!!FOR DJGPP ONLY!!! 

   video_buffer = (unsigned char *)( 0xa0000 + __djgpp_conventional_base ); 

// clear video memory

   memset( video_buffer, 0, 64000 ); 
}; 

/* 
 * set up of palette
 */ 

void VGA::SetColour( unsigned char i, unsigned char r, unsigned char g, unsigned char b ) 
{ 
// enter colour index
     outp( 0x3c8, i ); 
// and RGB component
     outp( 0x3c9, r ); 
     outp( 0x3c9, g ); 
     outp( 0x3c9, b ); 
}; 

/* 
 * return to text mode
 */ 
VGA::~VGA() 
{ 
// Set 0x03 mode
   union REGS regs; 
   regs.x.ax = 0x03; 
   int86( 0x10, ®s, ®s ); 
// return memory checking
   __djgpp_nearptr_disable(); 
// clear memory
   delete [] (page_draw); 
}; 

/* 
 * copy buffer on screen
 */ 
void VGA::Update() 
{ 
   while (inportb(0x3da) & 0x08); 
   while (!(inportb(0x3da) & 0x08)); 
   memcpy( video_buffer, page_draw, 64000 ); 
}; 

/* 
 * draw a pixel in temporary buffer
 */ 
void VGA::PutPixel( int x, int y, unsigned c ) 
{ 
   if ((x<0) || (x>319) || (y<0) || (y>199)) return; 
   page_draw[(y<<8)+(y<<6)+x] = c;  // same as y*320+x, but slightly quicker 
} 

/* 
 * Clear buffer
 */ 
void VGA::Clear() 
{ 
   memset( page_draw, 0, 64000 ); 
} 

-=[cut here]=- 

And vga.h:

-=[vga.h]=- 

#ifndef __VGA_H_ 
#define __VGA_H_ 

class VGA 
{ 
   public: 
   VGA(); 
   ~VGA(); 
   void Update(); 
   void PutPixel( int x, int y, unsigned c ); 
   void SetColour( unsigned char i, unsigned char r, unsigned char g, unsigned char b ); 
   void Clear(); 
   unsigned char *page_draw, 
                 *video_buffer; 

}; 

#endif 

-=[cut here]=- 

That's all for today! Wait for next HUGI! We shall continue! The main thing - if you liked this article and you want to read next chapters then write me, please! aleksandr.cherepov@narva.ut.ee :-)

Happy Coding,

{Poisoned_Brain.}